﻿/**
转换器开发说明文档：https://docs.qq.com/doc/DWUFOVGFlTldKVnpE 此文档0.2对开发时注意的主要内容的说明。


 */

using QDAS;
using QDasConverter.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WindGoes6.Database;

namespace QDasConverter.Core
{
    /// <summary>
    /// 这个是转换器的核心业务模块，实现具体的转换方法，可以交给不同的多线程类来运行。
    /// </summary>
    public class ConvertBase
    {
        #region 属性
        /// <summary>
        /// 显示名称，必填信息。
        /// </summary>
        public string DisplayName = "Company";

        /// <summary>
        /// 每个转换器的键，原则上名称与类名相同，并在构造函数中使用 nameof(classname) 进行初始化。
        /// </summary>
        public string ConverterKey = "ConvertBase";


        /// <summary>
        /// 是否为测试模式，只要在程序目录下添加一个名称为 ATestFile.debug，则会进入测试模式。
        /// </summary>
        public static bool DebugMode = false;

        /// <summary>
        /// 版本号，必填信息。如，alpha1 或 v1.2.1。
        /// 转换器的版本信息，每家公司使用自己的版本号，如果为空则使用转换器的版本号。(不要带V）
        /// </summary>
        public string Version = "V1.0.0";

        /// <summary>
        /// 在开始转换前是否进行连接检测，默认值为True。
        /// </summary>
        public bool NeedConnectionCheck = true;


        /// <summary>
        /// 文件日志列表，转换后可读取此列表。
        /// </summary>
        public List<TransLog> LogList = new List<TransLog>();

        /// <summary>
        /// 待执行的SQL语句。
        /// </summary>
        public string SqlCommand = null;

        /// <summary>
        /// 数据库连接字符串，如果此值不为空，则连接时最优先使用时字符串。
        /// </summary>
        public string SqlConnectionString = null;

        /// <summary>
        /// 最后一个处理的Result表的ID。
        /// </summary>
        public int LastResID = 0;

        /// <summary>
        /// 转换完成的情况。
        /// </summary>
        public double Percentage = 0;

        /// <summary>
        /// 每次转换输出的文件列表。
        /// </summary>
        public List<string> ResultFiles = new List<string>();

        /// <summary>
        /// 最后一次输出的DFQ文件。
        /// </summary>
        public string LastOutputDfqFile;

        /// <summary>
        /// 临时文件夹的位置，此文件夹必需已经存在，否则程序报错。
        /// </summary>
        public string TempDirectory = "./temp";

        /// <summary>
        /// 输出文件位置。此文件夹必需已经存在，否则程序报错。
        /// </summary>
        public string OutputDirectory = "./output";

        /// <summary>
        /// 当文件转换完成时发生此事件。
        /// </summary>
        public event TransFileCompleteEventHandler OneStepProcessCompleted;


        /// <summary>
        /// 转换器扩展名，以点号分隔，必填信息，如 "xls.xlsx"，默认情况下不使用。
        /// </summary>
        public string Extensions;

        /// <summary>
        /// 用于读取配置信息，默认为null，需要在initialize中初始化。
        /// </summary>
        public IniAccess ia = new IniAccess();

        /// <summary>
        /// 默认的SQLManager。
        /// </summary>
        public SQLManager sm;

        /// <summary>
        /// 上下限需要用的。
        /// </summary>
        public SQLManager psm;

        /// <summary>
        /// 每次转换的数量。
        /// </summary>
        public int BatchCount = 50;

        /// <summary>
        /// 每次转换时最小的转换数量，值为0时表示不限制，默认值为100。
        /// </summary>
        public int MinConvertCount = 0;

        /// <summary>
        /// 每两次转换的时间间隔，单位：秒，默认值为60。设置时间间隔的目的是为了避免过于频繁的数据库访问给服务器带来过高的压力。
        /// 实际上，过高的频率也没有意义，因为数据写入频率也是有限的。
        /// </summary>
        public int ConvertInterval = 60;

        /// <summary>
        /// 记录转换信息。每条记录由编号(Res_ID决定),时间, 事件类型，内容组成。
        /// </summary>
        public List<TransLog> ConversionMessages = new List<TransLog>();

        /// <summary>
        /// 需要进行数据转换的表的名子，即查询 select * from Result 中的 Result。
        /// </summary>
        public string TableName = "Result";

        /// <summary>
        /// 监听的表的主键的名子，如常用的 RES_ID
        /// </summary>
        public string TableIDName = "RES_ID";

        /// <summary>
        /// 2022/01/15加入用于随机生成一个随机数。
        /// </summary>
        public static Random random = new Random();
        #endregion

        /// <summary>
        /// 初始化ConvertBase。
        /// </summary>
        public ConvertBase() { }

        #region 核心处理方法

        public void print(string msg)
        {
            Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}\t{GetType().FullName}\t{msg}");
        }

        /// <summary>
        ///  Initialize transducer.
        /// </summary> 
        public virtual void Initialize()
        {
            ia = new IniAccess("config.ini");
            print("Initialize()");
        }

        /// <summary>
        /// 用于获得指定类的配置文件。
        /// </summary>
        /// <param name="converter"></param>
        /// <returns></returns>
        public virtual string GetConfigurefileName()
        {
            return this.GetType().Name + "_config.ini";
        }

        public virtual void OneLoop_Prepare()
        {
            print("OneLoop_Prepare()");
        }

        public virtual void OneLoop_Process()
        {
            print("OneLoop_Process()");
        }

        public virtual void OneLoop_Completed()
        {
            print("OneLoop_Completed()");
        }

        /// <summary>
        /// 根据输入的表名和指定的id判断是否有新数据。
        /// </summary>
        /// <param name="table"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public int GetNewRecordCount(string table, string id)
        {
            sm.CommandText = $"SELECT COUNT({id}) from {table} where {id} > {LastResID}";
            return int.Parse("0" + sm.GetObject());
        }

        /// <summary>
        /// 检测SQL连接是否正常。
        /// </summary>
        public bool IsConnectionValid()
        {
            // cs = "Data Source = 117.71.62.132,8092;Initial Catalog = A30;User ID = sa; Password = Huaun@2021;";
            string cs = ia.ReadString(Config.ConnectionString, true);
            if (string.IsNullOrEmpty(cs))
                return false;

            SqlConnectionString = cs;
            SQLManager.CommonConnectionString = cs;
            SQLManager dm = new SQLManager(SqlConnectionString);
            bool res = false;
            try
            {
                res = dm.Open();
                dm.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            print("OneLoop_Completed(): " + res);
            return res;
        }

        /// <summary>
        /// 判断是否需要开始下次循环，判断的原理是 TableName.TableIDName > LastResID + BatchCount
        /// </summary>
        /// <returns></returns>
        public virtual bool NeedConvert()
        {
            try
            {
                sm.CommandText = $"Select top 1 {TableIDName} from {TableName} where {TableIDName} > {LastResID + BatchCount} order by {TableIDName}";
                return sm.GetStrings().Length > 0;
            }
            catch { }
            return false;
        }


        /// <summary>
        /// 如果curID存在或者curID后面没有ID，则返回curID，否则返回ID后面最近的一个ID。
        /// </summary>
        /// <returns></returns>
        public int GetNextID(int curID)
        {
            sm.CommandText = $"SELECT TOP 1 {TableIDName} FROM {TableName} WHERE {TableIDName} >= {curID} Order BY {TableIDName}";
            var data = sm.GetStrings();
            return data.Length > 0 ? int.Parse(data[0][0]) : 0;
        }

        /// <summary>
        /// 返回值表示
        /// </summary>
        /// <returns></returns>
        public virtual bool OnStart()
        {
            if (ResultFiles == null)
                ResultFiles = new List<string>();
            ResultFiles.Clear();
            return true;
        }

        public virtual void OnStopped()
        {
            print("OnStopped()");
        }

        public virtual bool IsLoopCompleted()
        {
            print("IsLoopCompleted()");
            return false;
        }
        #endregion

        /// <summary>
        /// 清除省时文件夹中的数据。
        /// </summary>
        /// <param name="tempDirectory"></param>
        public void CleanTempFolder(string tempDirectory)
        {
            foreach (var file in Directory.GetFiles(tempDirectory))
                File.Delete(file);
        }


        /// <summary>
        /// 获得指定配置文件名的配置路径，名称中不带后缀，如 C:\\Gitee.com\\tsingshan-converter\\SQLConverter\\output\\configs\\C2021T10_SPH
        /// </summary>
        /// <param name="converterKey"></param>
        /// <returns></returns>
        public string GetConfigurefileFullpath(string converterKey, string configDirectory = ".\\configs")
        {
            return Path.Combine(Path.GetFullPath(configDirectory), converterKey);
        }

        /// <summary>
        /// 转换函数主体。
        /// 2020/11/10 标注：保存时直接调用基类函数：SaveDfq(qf, outpath);
        /// </summary>
        /// <param name="inpath"></param>
        /// <param name="outpath"></param>
        /// <returns></returns>
        public virtual bool Convert(string inpath, string outpath)
        {
            return true;
        }

        /// <summary>
        /// 返回转换器信息，格式为：公司名称||版本号||扩展名
        /// </summary>
        /// <returns></returns>
        public string GetInfoString()
        {
            return $"{DisplayName}||{Version}||{Extensions}";
        }


        /// <summary>
        /// 根据文件名返回输出全路径。同时如果发现输出不是以 .dfq 结尾的，会自动追加 .dfq。
        /// 本函数为虚函数，具体类在输出时如果对文件名有要求，可以重载编写。
        /// </summary>
        /// <param name="filename">文件名，可以不带 .dfq。</param>
        /// <returns></returns>
        public virtual string GetOutputDfqFilepath(string filename)
        {
            var output_filepath = Path.Combine(OutputDirectory, filename);
            return output_filepath.ToLower().EndsWith(".dfq") ? output_filepath : output_filepath + ".dfq";
        }

        /// <summary>
        /// 保存DFQ文件。
        /// </summary>
        /// <param name="qf">待保存的QFile对象。</param>
        /// <param name="output_filepath">DFQ输出路径。</param>
        public string SaveDFQToFile1(QFile qf, string output_filepath)
        {
            string folder = OutputDirectory + '\\' + qf[1053].ToString();
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);

            // 所有参数编号为从1开始的自增长  
            qf.ToDMode();

            // 输出的不包括后缀的全文件名，如  d:\output\QDAS001\20211113_081400.3322.dfq 
            string filename = Path.Combine(folder, $"{DateTime.Now:yyyyMMdd_HHmmss.ffff}.dfq");
            File.WriteAllText(filename, qf.GetKString(), Encoding.UTF8);
            return filename;
        }


        /// <summary>
        ///  将QFile对象qf输出至指定目录 filepath，并将文件全路径名添加至ResultFiles。若不存在后缀.dfq，则自动添加。
        /// </summary>
        /// <param name="qf">待输出QFile对象。</param>
        /// <param name="filepath">完整文件路径。</param>
        /// <returns></returns>
        public string SaveToDFQ(QFile qf, string filepath)
        {
            if (!filepath.ToLower().EndsWith(".dfq"))
                filepath += ".dfq";

            // 所有参数编号为从1开始的自增长  
            qf.ToDMode();

            filepath = Path.GetFullPath(filepath);
            if (qf.SaveToFile(filepath))
            {
                ResultFiles.Add(filepath);
            }
            return filepath;
        }

        /// <summary>
        /// 将QFile对象qf输出至指定目录outdir下，文件名为 outfilename，并将文件全路径名添加至ResultFiles。若不存在后缀.dfq，则自动添加。
        /// </summary>
        /// <param name="qf">待输出QFile对象。</param>
        /// <param name="outdir">输出文件夹。</param>
        /// <param name="outfilename">输出文件名。</param>
        /// <returns></returns>
        public string SaveToDFQ(QFile qf, string outdir, string outfilename)
        {
            return SaveToDFQ(qf, Path.Combine(outdir, outfilename));
        }

        #region 可重载函数

        /// <summary>
        /// 更新LastResID，默认规则为 LastResID += BatchCount
        /// </summary>
        public virtual void UpdateLastResId()
        {
            LastResID += BatchCount;
        }



        /// <summary>
        /// 使用指定格式返回当前时间的字符串，默认格式为 yyyyMMdd_HHmmss.fff。
        /// </summary>
        /// <param name="dateformat">时间格式，默认值为 yyyyMMdd_HHmmss.fff。</param>
        /// <returns></returns>
        public string NowString(string dateformat = "yyyyMMdd_HHmmss.fff")
        {
            return DateTime.Now.ToString(dateformat);
        }


        /// <summary>
        /// 转换文件函数，一般只需要重写这个函数即可，会被DealFile()调用。
        /// </summary>
        /// <param name="path">需要转换的文件路径。</param>
        /// <returns></returns>
        public virtual bool TransferFile(string path)
        {

            return true;
        }


        public void AddLog(LogType logType, string message, string input = "-", string output = "-", string remark = "-")
        {
            LogList.Add(new TransLog(logType, message, input, output, remark));
        }

        
        /// <summary>
        /// 给指定的输入路径的文件名加上时间戳，示例 c:\input\input.txt -> c:\input\input_20201107_215432.txt
        /// </summary>
        /// <param name="filepath">待添加时间戳的文件路径。</param>
        /// <returns></returns>
        private string AddTimeTickToFile(string filepath)
        {
            string filename = Path.GetFileNameWithoutExtension(filepath);
            string extension = Path.GetExtension(filepath);
            string dir = Path.GetDirectoryName(filepath);
            string timetick = DateTime.Now.ToString("yyyyMMdd_HHmmss");
            return Path.Combine(dir, $"{filename}_{timetick}{extension}");
        }

        /// <summary>
        /// 在备份时，如果文件名需要修改，重写此函数。
        /// </summary>
        /// <param name="outfile"></param>
        /// <returns></returns>
        public virtual string ModifyOutputFilePathWhenBackup(string outfile)
        {

            return outfile;
        }

        /// <summary>
        /// 读取配置路径文件。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static List<string> ReadDataPaths(string filepath)
        {
                var list = new List<string>();
                foreach (var line in File.ReadLines(filepath, Encoding.UTF8))
                {
                    // 去除结尾的#号表示的注释
                    string path = line.Trim().Split('#')[0];

                    // 长度为0，或以#表示注释，添加字符串中井号左侧的全部内容
                    if (path.Length > 0 && !path.StartsWith("#"))
                        list.Add(path);
                }
            return list;
        }       
    


        /// <summary>
        /// 将输入文件移动到指定的目录，文件名不变。
        /// </summary>
        /// <param name="srcfilepath">输入文件。</param>
        /// <param name="targetdir">目标文件夹。</param>
        /// <param name="deleteScr">是否删除原文件，默认值为false。</param>
        public void CopyFileTo(string srcfilepath, string targetdir, bool deleteScr = false)
        {
            var filedir = Path.GetDirectoryName(srcfilepath);
            var filename = Path.GetFileName(srcfilepath);
            if (!File.Exists(srcfilepath))
            {


                File.Copy(srcfilepath, Path.Combine(targetdir, filename));
            }

            if (deleteScr)
                File.Delete(srcfilepath);
        }

        public double GetMidValue(List<double> values) {
            var length = values.Count; 
            if(length < 1) return 0.0;
            if (length % 2 == 0)
            {
                return (values[length / 2] + values[(length / 2)-1])/2;
            }
            else { 
                return values[(length-1) / 2];
            }
        }
        #endregion
    }
}
